/*  _                             
   /_\    _ _   _ _   __ _   _  _ 
  / _ \  | '_| | '_| / _` | | || |
 /_/ \_\ |_|   |_|   \__,_|  \_, |
                             |__/ 						 
*/
Array.prototype.chunk = function(chunkSize) {
    var array=this;
    return [].concat.apply([],
        array.map(function(elem,i) {
            return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
        })
    );
}
Array.prototype.remove = function(element){
	var l = this.length;
	while(l--){
		if(this[l] === element){
			this.splice(l,1);
		}
	}
	return this;
}


/*
  ___                      _     _              
 | __|  _  _   _ _    __  | |_  (_)  ___   _ _  
 | _|  | || | | ' \  / _| |  _| | | / _ \ | ' \ 
 |_|    \_,_| |_||_| \__|  \__| |_| \___/ |_||_|
                                                
*/
Function.prototype.args = function(){
	return this.toString().slice(this.toString().indexOf('(')+1, this.toString().indexOf(')')).match(/([^\s,]+)/g);		
}



/*
   ___    _        _              _   
  / _ \  | |__    (_)  ___   __  | |_ 
 | (_) | | '_ \   | | / -_) / _| |  _|
  \___/  |_.__/  _/ | \___| \__|  \__|
                |__/                  
*/
Object.prototype.on = function(type, data, listener){	
	if(!this._listeners){
		this._listeners = [];	
	}
	if (arguments.length == 1){ throw new {message:"error on addListener: not enough arguments"}};
	if (arguments.length == 2){
		this._listeners.push({type:arguments[0], listener:arguments[1]});	
	}else{
		this._listeners.push({type:arguments[0], data:arguments[1], listener:arguments[2]});			
	}
}
Object.prototype.fire = function(type, data){
	if(!this._listeners){
		return;
	}
	var i = this._listeners.length;
	while(i--){
		var lstObj = this._listeners[i];
		if(lstObj.type == type){
			lstObj.listener.call(lstObj, type, data);
		}
	}		
}
Object.prototype.off = function(listener){
	if(!this._listeners){
		return;
	}
	var i = this._listeners.length ;
	while(i--){
		var lstObj = this._listeners[i];
		if(lstObj.listener == listener){
			 this._listeners.splice(i,1);
		}
	}	
};

Object.prototype.__constructor = function(a, b, c, d, e){}
Object.prototype.extend = function(classname){
    var sProto = this.prototype;
    eval(
        "if(typeof " + classname + " == 'undefined')" +
        classname + " = function(a, b, c, d, e){ this.__constructor(a, b, c, d, e); }"
    );
    eval("__tmpProto = " + classname + ".prototype;");
    var cProto = __tmpProto;
    for(attr in sProto) {
        var hasAttr = false;
        for(a in cProto) {
            if(attr == a){
                hasAttr = true;
            }
        }
        if(hasAttr === false){
            cProto[attr] = sProto[attr];
        }
    }
    __tmpProto = cProto;
    eval(classname + ".prototype = __tmpProto;");
    delete(__tmpProto);
}

Object.prototype.getClassName = function() {
    var funcNameRegex = /function (.{1,})\(/;
    var results = (funcNameRegex).exec((this).constructor.toString());
    return (results && results.length > 1) ? results[1] : "";
}



/* ___                   
 | _ )  __ _   ___  ___ 
 | _ \ / _` | (_-< / -_)
 |___/ \__,_| /__/ \___|
                        
*/
function Counter(){
	value = 0;
	
	function get(){
		return value;
	}
	function increment(check){
		check = check || +Infinity
		value = (value < check ? ++value : check );
		return this;
	}
	function decrement(check){
		check = check || -Infinity;
		value = (value > check ? --value : check);
		return this;
	}
	return {
		get:get,
		increment:increment,
		decrement:decrement	
	};
}

function Progress(name, initial, _table){
	var c = new Counter(name);
	var table = _table;
	
	function forward(){
		c.increment(table.length-1);
	};
	function backward(){
		c.decrement(0);
	};
	function get(){
		return table[c.get()];
	};
	function set(val, context){
		var cache = table[c.get()];
		this.fire('change',{type:'before', object: this, value: cache, 	futureValue: val, lastValue: cache  });
		table[c.get()] = val;
		this.fire('change',{type:'after',  object: this, value: val,	futureValue: val, lastValue: cache  });
	};
	function addValue(value){
		set.call(this,get()+value);
	}
	function getAll(){
		return table;
	};
	function use(tab){
		table = tab;
	};
	return{	
		forward:forward,
		backward:backward,
		set:set,
		get:get,
		addValue:addValue,
		getAll:getAll,
		use:use
	}
}




/*	expects following structure:

	[
		{
			name: string
			value: number|array[any]
		},	
	
	]
	
	value can be an array, in this case the 'level' method will be applied
*/
function Meter(/*array*/){
	
	
	//	the object to save the progresses to
	var progresses = {};
	
	
	//	instantiate the Meter with arguments, if given.
	if(arguments[0]){
		
		
		//	get the array with keys and tables/values
		var arr = arguments[0];
		var l = arr.length;
		
		
		//	loop
		while(l--){
			
			
			// cache
			var arg = arr[l];
			addProgress(arg.name, arg.value||0);
			
			
			//	if the arg.value is an array we apply levels instantly
			if(arg.value instanceof Array){
				
				
				// apply the array
				levels(arg.name, arg.value);
				
				
			}
			
			
		}
		
		
	}
	
	
	
	 //	add an all new progress to the meter,
	 //	thoose are used to track the meter status
	 // there can be as many Progresses in a Meter as u want
	 // in theory you could track all your unit data in one Meter
	 // but it is better to create more Meters to hold the listener-stack small
	function addProgress(name){
		
		
		// 	instantiate with zero value
		progresses[name] = createProgress();
		
		
		//	return progress for convenience
		return progresses[name];
		
		
	};
	
	
	
	//	shortcut
	function createProgress(){
		
		
		// create new empty progress
		return new Progress('', 0, []);
		
		
	};
	
	
	
	//	returns whether the key is available or not.
	function hasKey(key){
		
		
		// boolean
		return progresses[key]!='undefined';
		
		
	};
	
	
	
	//	add an key to the meter
	//	there several thtings that make big sense, eg. 'minimum', 'maximum'
	//	'current' or 'value'
	function addKey(key, val){
		
		
		// create Process and instantiate it with an value
		// NO Error if val is undefined
		addProgress(key).set(val);
		return this;
		
		
	};
	
	
	
	// 	remove key from Progresses
	// 	TODO: remove associated handlers
	// 	TODO: debug possible errors on missing keys
	function removeKey(key){
		
		
		// 	set progress to null
		progresses[key] = null;	
		return this;	
		
		
	};
	
	
	
	//	sets a new value for the progress 'key'
	//	e.g.:	set('maximum',300);
	//	can be chained
	function set(key, value){
		
		
		//	sets an value to the Progress
		//	Progress will trigger 'change' events 
		progresses[key].set(value);
		
		
		//return for convenience
		return this;
		
		
	};
	
	
	
	//
	function add(key, value){
		
		
		//
		progresses[key].addValue(value);
		return this;
		
		
	};
	
	
	
	// 	returns the current value of the key
	//	depending on the current level of the key in the meter
	function get(key){
		
		
		//	return the value of the Progress
		return progresses[key].get();
		
		
	};
	
	
	
	/*	
		upgrades the current key to the next level
		this allows for level advancements
		
			var lifeMeter = new Meter([{name:'lifevalue',value:[10,15,21,27,34,42,53]}]);
			lifeMeter.get('lifevalue')		//	returns 10
			lifeMeter.upgrade('lifevalue')
			lifeMeter.get('lifevalue')		//	returns 15 
	*/
	function upgrade(key){
		
		
		progresses[key].forward();	
		return this;
		
		
	};
	
	
	
	/*	
		sets a (new) table for level progressing
		
			var manaMeter = new Meter();
			manaMeter.addKey('max').levels('max',[100,150,210,280,360,450,550]);
			manaMeter.get('max')		// returns 100
			manaMeter.upgrade('max');
			manaMeter.get('max')		// returns 150
	*/
	function levels(key, table){
		
		
		/* 	
			force the progress to use the new table for progresion
			also updates the current progress value,
			
				var m = new Meter([{name:'val', value:0},{name:'max',value:[10,15,20]}]);
				m.upgrade('max').get('max')	//	returns 15
				m.levels('max',[12,17,22])
				m.get('max')				//	returns 17
		*/
		progresses[key].use(table);
		return this;
		
		
	};
	
	
	
	/*
		compare function to generate events
		eg.: value('exp') >= value('max') => event;
		
			var experienceMeter = new Meter();
			experienceMeter.addKey('val',0).addKey('max',100);
			experienceMeter.levels('max',[10,16,25,40,70,120,200,341,590]);
			experienceMeter.compare(function(a,b){return a >= b;},'val','max',function(e,d){
				if(e.type == 'after'){
					experienceMeter.upgrade('max');
					experienceMeter.set('val',0);
				}	
			});
			
			// unit kills enemy unit and gets experience points
			experienceMeter.add('val',5);
	*/
	function doCompare(fn, subjectKey, targetKey, callback){
		
		
		// catch the change event of the current progress
		progresses[subjectKey].on('change',function(e,d,a){
			
			
			// compare the value that will be with the value to be checked
			if(fn(d.futureValue , get(targetKey))){
				
				
				// call the callback
				callback({type: d.type, value: d.value, futureValue: d.futureValue});
			
			
			}
			
			
		})
	
	
	};
	
	
	
	
	function compare(sign, subjectKey, targetKey, callback){
		if(typeof(sign) == 'string'){
			switch(sign){
				case '<':
					doCompare(function(a,b){return a <  b;}, subjectKey,targetKey, callback);
				break;
				case '<=':
					doCompare(function(a,b){return a <= b;}, subjectKey,targetKey, callback);
				break;
				case '=<':
					doCompare(function(a,b){return a <= b;}, subjectKey,targetKey, callback);
				break;
				case '>':
					doCompare(function(a,b){return a >  b;}, subjectKey,targetKey, callback);
				break;
				case '>=':
					doCompare(function(a,b){return a >= b;}, subjectKey,targetKey, callback);
				break;
				case '=>':
					doCompare(function(a,b){return a >= b;}, subjectKey,targetKey, callback);
				break;
				case '=':
					doCompare(function(a,b){return a == b;}, subjectKey,targetKey, callback);
				break;
				case '==':
					doCompare(function(a,b){return a == b;}, subjectKey,targetKey, callback);
				break;
				default:
					console.log('did not have identifier', sign);
				break;
			}
		}else{
			if(typeof(sign) == 'function'){
				doCompare(sign, subjectKey, targetKey, callback);
			}else{
				console.log('sign must be &gt;, &lt; or &equiv; or a function taking to arguments to compare');
			}
		}
	};
	
	return{
		set:set,
		add:add,
		get:get,
		addKey:addKey,
		removeKey:removeKey	,
		hasKey:hasKey,
		upgrade:upgrade,
		levels:levels,
		compare:compare,
		g:function(key){return progresses[key];}
	}
}









function Table(){
	function custom(name, length, startValue, fn){
		var result = {name:name, value:[]};		
		var l = length;
		while(l--){
			var r = (startValue + fn(length-l-1, length, r));
			result.value.push(r);	
		}
		return result;		
	};
	function  linear(name, length, startValue, scale){
		return custom(name, length, startValue, function(i,l,p){return Math.round(i*scale)});		
	};
	function bilinear(name, length, startValue, scale1, scale2){
		return custom(name, length, startValue, function(i,l,p){return Math.round(i*i*scale1 + scale2*p)});
	};
	
	return{
		custom:custom,
		linear:linear,	
		bilinear:bilinear
    }
}

function Unit(){
	
}









function Color(){
	var result = "";
	function parseC(s){
		switch(typeof(s)){
			case 'number': 
				return s;
				break;
			case 'object':
				if(s.random){
					return Math.random()*(256 - s.random[0] - s.random[1]) + s.random[0] - 1;
				}else{
					if (s.set){
						return s.set[Math.floor(Math.random()*s.set.length)];
					}else{
						return 0;
					}
				}
				break;	
		}
	}
	var l = arguments.length;
	var ol = l-1;
	
	while(l--){
		var r = parseInt(parseC(arguments[ol-l])).toString(16);
		 r == '0' ? r = '0'+r : null;
		result += r;
	}
	return '#'+result;
}





function Util (element){
	if(element){
		var res = {};
		res.target = element;
		res.hoverObject = element;
		
		document.addEventListener('mouseover',function(e){
			res.hoverObject = e.target;
		});
		
		
		res.keys = {};
		res.keys.pressed = [];
		
		var keys = {};
		var key = function(char){
			var k = {};
			k.charCode = char;
			k.charValue = String.fromCharCode(l).toLowerCase();
			var kd = function(e){
				if(res.hoverObject == res.target && e.which == char){
					if(res.keys.pressed.indexOf(k) == -1) {res.keys.pressed.push(k)};
					k.fire('keydown',{originalEvent:e, targetObject:res, char:k});
				}
			};
			var ku = function(e){
				if(res.hoverObject == res.target && e.which == char){
					res.keys.pressed.remove(k);
					k.fire('keyup',{originalEvent:e, targetObject:res, char:k});
				}
			}
			
			document.addEventListener('keydown', kd);
			document.addEventListener('keyup', ku);
			k.KeyListeners = [];
			k.KeyListeners.push(kd);
			k.KeyListeners.push(ku);
			return k;
		};
		
		var l = 1024;
		while(l--){
			keys[l] = key(l);
		}
		res.key = function(char){
			switch (char) {
				case 'ARROW_UP':
					return keys[38];
					break;
				case 'ARROW_LEFT':
					return keys[37];
					break;
				case 'ARROW_RIGHT':
					return keys[39];
					break;
				case 'ARROW_DOWN':
					return keys[40];
					break;
			}
			if(typeof(char)=='string'){
					return keys[char.charCodeAt(0)-32];
				}else{
					return keys[char];
				}		
		}
	
		return res;
	}else{
		var res = {};
		var r = {};
		r.lastDrop = Date.now();
		r.samples = 0;
		r.maxSamples = 5;
		r.fps = 0;
		r.update = function(){
			r.samples++;
			if(r.samples >= r.maxSamples){
				r.samples = 0;
				var difference = Date.now() - r.lastDrop;
				r.lastDrop = Date.now();
				r.fps = r.maxSamples * 1000 / difference;
				r.fire('fps',r);
			}
		}
		
		res.FPS = r;
		return res;
	}
}












